package com.sinszm.common.util;

import org.springframework.util.StringUtils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.lang.reflect.UndeclaredThrowableException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;
import java.util.regex.Pattern;

/**
 * 常规工具
 *
 * @author chenjianbo
 */
public final class CommonUtils {

    private CommonUtils() {
    }

    private static final String REG_IPV4 = "^((25[0-5]|2[0-4]\\d|((1\\d{2})|([1-9]?\\d)))\\.){3}(25[0-5]|2[0-4]\\d|(" +
            "(1\\d{2})|([1-9]?\\d)))$";
    private static final String REG_DOMAIN = "^(?=^.{3,255}$)[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\\" +
            ".[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$";
    private static final String REG_MOBILE = "^1\\d{10}$";
    private static final String REG_NUMBER = "[0-9]+";

    /**
     * IP地址正则验证
     *
     * @param ip ip字符串
     * @return true有;false无
     */
    public static boolean isIPv4(String ip) {
        return StringUtils.hasText(ip) && Pattern.matches(REG_IPV4, ip);
    }

    /**
     * 域名地址正则验证
     *
     * @param domain 域名地址
     * @return true有;false无
     */
    public static boolean isDomain(String domain) {
        return StringUtils.hasText(domain) && Pattern.matches(REG_DOMAIN, domain);
    }

    /**
     * 验证手机号
     *
     * @param mobile 手机号
     * @return true有;false无
     */
    public static boolean isMobile(String mobile) {
        return StringUtils.hasText(mobile) && Pattern.matches(REG_MOBILE, mobile);
    }

    /**
     * 验证数字
     *
     * @param number 数字字符串
     * @return true有;false无
     */
    public static boolean isNumber(String number) {
        return StringUtils.hasText(number) && Pattern.matches(REG_NUMBER, number);
    }

    /**
     * 生成一次性密码(6位)
     *
     * @return 随机字符串
     */
    public static String getOneTimePassword() {
        return getOneTimePassword(6);
    }

    /**
     * 生成指定位数一次性密码
     *
     * @param length 长度：4，6，8，10
     * @return 随机字符串
     */
    public static String getOneTimePassword(int length) {
        return OneTimePwdUtil.generateTOTP(
                UUID.randomUUID().toString().replaceAll("-", ""),
                System.currentTimeMillis() + "",
                length
        );
    }

    /**
     * 生成MD5字符串
     */
    public synchronized static String MD5(String arg) {
        try {
            char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
                    'a', 'b', 'c', 'd', 'e', 'f'};
            byte[] strTemp = arg.getBytes(StandardCharsets.UTF_8);
            MessageDigest mdTemp = MessageDigest.getInstance("MD5");
            mdTemp.update(strTemp);
            byte[] md = mdTemp.digest();
            int j = md.length;
            char[] str = new char[j * 2];
            int k = 0;
            for (byte byte0 : md) {
                str[k++] = hexDigits[byte0 >>> 4 & 0xf];
                str[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(str);
        }catch (Exception e) {
            e.printStackTrace(System.err);
        }
        return "";
    }

    /**
     * SHA1签名方式
     */
    public synchronized static String SHA1(String source) {
        final char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8',
                '9', 'A', 'B', 'C', 'D', 'E', 'F'};
        try {
            final MessageDigest mdTemp = MessageDigest.getInstance("SHA1");
            mdTemp.update(source.getBytes("UTF-8"));
            byte[] md = mdTemp.digest();
            int j = md.length;
            char[] buf = new char[j * 2];
            int k = 0;
            for (byte byte0 : md) {
                buf[k++] = hexDigits[byte0 >>> 4 & 0xf];
                buf[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(buf);
        } catch (Exception e) {
            return "";
        }
    }

    /**
     * Base64 编码
     */
    public static String base64Encode(String str) {
        return Base64.getEncoder().encodeToString(
                str.getBytes(StandardCharsets.UTF_8)
        );
    }

    /**
     * Base64 解码
     */
    public static String base64Decode(String str) {
        return new String(
                Base64.getDecoder().decode(str),
                StandardCharsets.UTF_8
        );
    }

    /**
     * 时间格式化为字符串精确到毫秒
     * yyyy-MM-dd HH:mm:ss.SSS
     */
    public static String formatTime2Millisecond(Date date) {
        return String.format("%tF %tT.%tL", date, date, date);
    }

    /**
     * 时间格式化为字符串精确到秒
     * yyyy-MM-dd HH:mm:ss
     */
    public static String formatTime2Second(Date date) {
        return String.format("%tF %tT", date, date);
    }

    /**
     * 时间格式化为字符串精确到日
     * yyyy-MM-dd
     */
    public static String formatTime2Day(Date date) {
        return String.format("%tF", date);
    }

    /**
     * 计算两个日期之间相差的天数
     */
    public static long daysBetween(Date start, Date end) {
        LocalDateTime arg0 = LocalDateTime.ofInstant(start.toInstant(), ZoneId.systemDefault());
        LocalDateTime arg1 = LocalDateTime.ofInstant(end.toInstant(), ZoneId.systemDefault());
        return Duration.between(arg0, arg1).toDays();
    }

    /**
     * 计算两个日期之间相差的小时数
     */
    public static long hoursBetween(Date start, Date end) {
        LocalDateTime arg0 = LocalDateTime.ofInstant(start.toInstant(), ZoneId.systemDefault());
        LocalDateTime arg1 = LocalDateTime.ofInstant(end.toInstant(), ZoneId.systemDefault());
        return Duration.between(arg0, arg1).toHours();
    }

    /**
     * 元数据添0补位
     *
     * @param arg 源数据
     * @param len 位数
     * @return 结果
     */
    public synchronized static String frontCompWithZore(int arg, int len) {
        return String.format("%0" + len + "d", arg);
    }

    private static final class OneTimePwdUtil {

        private static final int[] DIGITS_POWER = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000};

        private static byte[] hmacSha(String crypto, byte[] keyBytes, byte[] text) {
            try {
                Mac hmac;
                hmac = Mac.getInstance(crypto);
                SecretKeySpec macKey = new SecretKeySpec(keyBytes, "RAW");
                hmac.init(macKey);
                return hmac.doFinal(text);
            } catch (GeneralSecurityException gse) {
                throw new UndeclaredThrowableException(gse);
            }
        }

        private static byte[] hexStr2Bytes(String hex) {
            byte[] bArray = new BigInteger("10" + hex, 16).toByteArray();
            byte[] ret = new byte[bArray.length - 1];
            System.arraycopy(bArray, 1, ret, 0, ret.length);
            return ret;
        }

        static String generateTOTP(String key,
                                   String time,
                                   int digits) {
            return generateTOTP(key, time, digits, "HmacSHA1");
        }

        public static String generateTOTP256(String key,
                                             String time,
                                             int digits) {
            return generateTOTP(key, time, digits, "HmacSHA256");
        }

        public static String generateTOTP512(String key,
                                             String time,
                                             int digits) {
            return generateTOTP(key, time, digits, "HmacSHA512");
        }

        static String generateTOTP(String key,
                                   String time,
                                   int digits,
                                   String crypto) {
            StringBuilder result;

            StringBuilder timeBuilder = new StringBuilder(time);
            while (timeBuilder.length() < 16) {
                timeBuilder.insert(0, "0");
            }
            time = timeBuilder.toString();

            byte[] msg = hexStr2Bytes(time);
            byte[] k = hexStr2Bytes(key);

            byte[] hash = hmacSha(crypto, k, msg);

            int offset = hash[hash.length - 1] & 0xf;

            int binary = ((hash[offset] & 0x7f) << 24) | ((hash[offset + 1] & 0xff) << 16)
                    | ((hash[offset + 2] & 0xff) << 8)
                    | (hash[offset + 3] & 0xff);

            int otp = binary % DIGITS_POWER[digits];

            result = new StringBuilder(Integer.toString(otp));
            while (result.length() < digits) {
                result.insert(0, "0");
            }
            return result.toString();
        }

    }
}
